/**
 * slide show engine
 * @author Bruce Li
 * @copyright centrebet.com
 * @class QSLIDE, QSLIDE_CONTAINER, QSLIDE_SLIDE, QSLIDE_FX, QSLIDE_NAV
 */
/**
 * slide main class
 * @static
 */
var QSLIDE = {
	// group of containers
    group : {},

    logOn : false,

    // start reading by using classname
    init : function (containerClass, slideClass, logOn) {
    	// loop and make...
    	var group = {};
    	$('.' + containerClass).each(function(index, domEle) {
    		// call each init...
    		group[$(domEle).attr('id')] = new QSLIDE_CONTAINER($(domEle), slideClass);
    	});
    	this.group = group;

    	// log on...
    	if (logOn) {
    		this.logOn = true;
    	} else {
    		this.logOn = false;
    	}

    	QSLIDE.log('initiated!');
    },

    log : function(val) {
    	if (this.logOn) {
    		if (window.console != undefined) {
    			return console.log(val);
    		}
    	}
    },

    // get container by id
    getContainer : function (id) {
    	return this.group[id];
    },

    // get slide by container id and slide id...
    getSlide : function (containerId, slideId) {
    	return this.group[containerId].slides[slideId];
    },

    hideSlide : function (containerId, slideId) {
    	QSLIDE.log('hide slide: ' + containerId + ' - ' + slideId);
    	return this.group[containerId].hide(slideId);
    },

    showSlide : function (containerId, slideId) {
    	QSLIDE.log('show slide: ' + containerId + ' - ' + slideId);
    	return this.group[containerId].show(slideId);
    },

    removeSlide : function (containerId, slideId) {
    	QSLIDE.log('remove slide: ' + containerId + ' - ' + slideId);
    	return this.group[containerId].remove(slideId);
    },

    addSlide : function (containerId, html) {
    	QSLIDE.log('add slide: ' + containerId + ' - ' + html);
    	return this.group[containerId].add(html);
    },

    // short hand for get container
    c : function(id) {
    	return this.getContainer(id);
    },

    /**
     * start slide
     * @param id the id
     * @param callback the call back function
     * @return void
     */
    start : function (id, callback) {
    	QSLIDE.log('start slide: ' + id);
    	if (!this.c(id)) {
    		return QSLIDE.log(id + ' doesnt exist...');
    	}
    	return this.c(id).start(callback);
    },

    /**
     * stop slide
     * @param id the id
     * @return void
     */
    stop : function(id) {
    	QSLIDE.log('stop slide: ' + id);
    	if (!this.c(id)) {
    		return QSLIDE.log(id + ' doesnt exist...');
    	}
    	return this.c(id).stop();
    },

    /**
     * pause slide
     * @param id the id
     * @return void
     */
    pause : function(id) {
    	QSLIDE.log('pause slide: ' + id);
    	if (!this.c(id)) {
    		return QSLIDE.log(id + ' doesnt exist...');
    	}
    	return this.c(id).pause();
    },


    /**
     * reorder
     * @param id the id
     * @param arrayIds an array of ids for the slides...
     * @return void
     */
    shuffle : function(id, arrayIds) {
    	QSLIDE.log('shuffle slide: ' + id);
    	if (!this.c(id)) {
    		return QSLIDE.log(id + ' doesnt exist...');
    	}
    	if (arrayIds) {
    		QSLIDE.log(arrayIds);
    	}
    	return this.c(id).shuffle(arrayIds);
    }
};


/**
 * effects + attributes
 * @param speed the transition speed
 * @param interval how long does it stay?
 * @param easing type of easing
 */
var QSLIDE_FX = function(speed, interval, easing)
{
	// defaults
	// 1.5 seconds transition
	if (!speed) {
		speed = 1500;
	}
	// 5 seconds interval
	if (!interval) {
		interval = 5000;
	}
	// linear easing
	if (!easing) {
		easing = 'swing';
	}
	this.easing   = easing;
	this.interval = interval;
	this.speed    = speed;

	// functions...
	this.setSpeed = function (val) {
		this.speed = val;
	};
	this.setInterval = function (val) {
		this.interval = val;
	};
	this.setEasing = function (val) {
		this.easing = val;
	};
};


/**
 * slide navigation object
 * @param container the container to hold it
 * @param template  the template of label(link)
 * @param glue      the separator, such as | for the link...
 */
var QSLIDE_NAV = function (container, template, glue)
{
	QSLIDE.log('start new QSLIDE_NAV:');
	this.container = container;

	// defaults...
	if (!template) {
		template = '<a href="#" title=":title" class=":highlight" onclick=":link">:title</a>';
	}

	if (!glue) {
		glue = ' | ';
	}

	this.template  = template;
	this.glue      = glue;
	QSLIDE.log(this);

	/**
	 * paint the nav...
	 */
	this.paint = function(slideContainer) {
		// use the div to paint...
		var slides = slideContainer.activeSlides;
		var len    = slides.length;
		var src    = '';
		for (var i = 0; i < len; i ++) {
			// replace and paint
			var title     = slides[i].title;
			var highlight = (slides[i].order == slideContainer.currentSlide.order) ? 'active' : '';
			var link      = "QSLIDE.getContainer('" + slideContainer.id +
							"').jump('" + slides[i].id + "'); return false;";
			var item = this.template.replace(/:title/g, title);
			item     = item.replace(/:highlight/g, highlight);
			item     = item.replace(/:link/g, link);
			src 	+= item;
			if (i < (len - 1)) {
				src += this.glue;
			}
		}
		// paint...
		this.container.html(src);
		src = null;
	};
};


/**
 * container class
 * @param obj the container object
 * @param slideClass class name of the slider
 */
var QSLIDE_CONTAINER = function(obj, slideClass)
{
	// attributes...
	// 1. properties
	this.id     = obj.attr('id');
    this.width  = parseInt(obj.css('width'));
    this.height = parseInt(obj.css('height'));
    // add wrappa
    this.wrapperId = '_qslide_wrapper_' + this.id;

    // flags
    this.loaded    = true;	// default, if no text
    this.willStart = false;
    this.isStarted = false;

    // fix properties...
    obj.css('overflow', 'hidden');
    obj.css('position', 'relative');

    // 2. slides in two containers
    this.slides 	  = {}; // object: id -> QSLIDE_SLIDE *slide object
    this.activeSlides = [];	// plain array: QSLIDE_SLIDE *slide object

    // 3. effects and intervals...
    this.fx = new QSLIDE_FX();	// use default for all

    // 4. navigation...
    this.nav   = false;
    this.navId = false;

    /**
     * get node out
     * @return html element node
     */
    this.node = function() {
    	return $('#' + this.id);
    };

    // preload images...
    this.images = [];
    this.addImages = function(images) {
    	// add to self attr
    	QSLIDE.log('add images');
    	QSLIDE.log(images);
    	this.images = $.merge(this.images, images);
    };

    // get all cont from obj, then destroy it... not reversible
    var slides    = {};
    var ids       = [];
    var order     = 0;
    var container = this;
    // start adding childern
    obj.children('.' + slideClass).each(function(index, domEle) {
		// loop a loop
		var id = $(domEle).attr('id');
		QSLIDE.log('create new slide: ');
		slides[id] = new QSLIDE_SLIDE($(domEle), container, 0);
		QSLIDE.log(slides[id]);
		if (slides[id].visible) {
			slides[id].order = order;
			ids[order] = slides[id];
			order ++;
		}
	});

    // map to the thingy...
    this.slides 	  = slides;
    this.activeSlides = ids;
    this.currentSlide = false;
    // clean up the div...
    QSLIDE.log('clean up the div');
    obj.html(''); obj = null; slides = ids = null;

    // show it...
    this.node().css('display', 'block');

    // onload function
    this.onload = function(){};	// can be overriden by users
    var selfId = this.id;
    var externalOnloadFunction = this.onload;
    this.onloadCallback = function(){
    	// note: reference it from out side...
    	QSLIDE.log('loaded!');
    	var self = QSLIDE.c(selfId);
    	self.loaded = true;
    	// first of all... build it!
    	self.build();

    	// will it start?
    	if (self.willStart) {
    		self.run();
    		self.willStart = false;
    	}
    	// run external onload...
    	QSLIDE.log('call onload external function');
    	externalOnloadFunction();
    };

    /*-----------------------------[core]-----------------------------*/
    /**
     * build the slides
     * @return void
     */
    this.build = function() {
    	// just rebuild and paint...
    	var wrapperWidth = 0;
    	var length = this.width;
    	var height = this.height;
    	var tmpSrc = '';
    	var order = 0;
    	$(this.activeSlides).each(function() {
    		tmpSrc += this.build();
    		this.order = order;
    		order ++;
    		wrapperWidth += length;
    	});
    	// compose
    	tmpSrc = '<div id="' + this.wrapperId
    			 + '" style="left:0;top:0;position:absolute;overflow:hidden;width:'
    			 + wrapperWidth
    			 + ';height:'
    			 + height
    			 + ';">'
    			 + tmpSrc + '</div>';
    	this.node().html(tmpSrc);
    	tmpSrc = null;
    	QSLIDE.log('tmp source of slides built');

    	// after building, the first slide is the current slide...
        this.currentSlide = this.activeSlides[0];
    };

    /**
     * load images
     * @return void
     */
    this.load = function() {
    	// preload images...
        if (this.images.length > 0) {
        	this.loaded = false;
    	    var node = this.node();
    	    var total = this.images.length;
    	    node.addClass('loading');
    	    // loop to load each image separately...
    	    var callback = this.onloadCallback;
    	    $(this.images).each(function() {
    	    	var src = $(this).attr('src');
        		// destroy current image...
        		$(this).removeAttr('src');
        		QSLIDE.log('loading image: ' + src);
    	    	// load...
                var loader = new Image();
                var callbackFunc = callback;
    	    	$(loader).attr('src', src).load(function() {
    	    		total = total - 1;
    	    		if (total == 0) {
    	    			// all loaded...
    	        	    node.removeClass('loading');
    	        	    // call exteranl onload function...
    	        	    callbackFunc();
    	        	    QSLIDE.log('image loaded!');
    	    		}
    	    	});
    	    });
        } else {
        	QSLIDE.log('no images found, start right away');
        	// then build...
        	this.loaded = true;
        	// first of all... build it!
        	this.build();
        }
    };

    // init, so start it...
    this.load();

    /*-----------------------------[slide control]-----------------------------*/
    /**
     * show slide
     * @return void
     */
    this.show = function(id) {
    	if (!this.slides[id] || !this.loaded) {
    		return this;
    	}
    	QSLIDE.log('show slide by id: ' + id);
    	if (!this.slides[id].visible) {
	    	this.slides[id].show();
	    	// need reset...
	    	return this.shuffle();
    	}
    };


    /**
     * hide slide
     * @return void
     */
    this.hide = function(id) {
    	if (!this.slides[id] || !this.loaded) {
    		return this;
    	}
    	QSLIDE.log('hide slides by id: ' + id);
    	if (this.slides[id].visible) {
	    	this.slides[id].hide();
	    	// need reset...
	    	return this.shuffle();
    	}
    };


    /**
     * kill one slide forever
     * @return this
     */
    this.remove = function(id) {
    	if (!this.slides[id] || !this.loaded) {
    		return this;
    	}
    	// note: irreversible
    	QSLIDE.log('WARNING::remove slide by id: ' + id);
    	if (this.slides[id]) {
    		this.slides[id] = false;
    	}
    	// shuffle...
    	return this.shuffle();
    };


    /**
     * add a new div with given html
     * @return this
     */
    this.add = function(divHtml) {
    	// must be unique id, otherwise replace...
    	QSLIDE.log('add new slide with html: ' + divHtml);
    	var obj = $(divHtml);
//    	console.log('add: ', divHtml);
//    	console.log(obj.attr('id'));
    	var slide = new QSLIDE_SLIDE(obj, this, this.activeSlides.length + 1);
    	// so it can replace ...
    	this.slides[obj.attr('id')] = slide;
    	// then shuffle...
    	this.shuffle();
    };

    /**
     * shuffle the cards...
     * @return this
     */
    this.shuffle = function (ids) {
    	// first, complete stop
    	QSLIDE.log('shuffle the cards');
    	if (this.isStarted) {
    		this.stop();
    		this.willStart = true;
    	}

    	this.activeSlides = [];
		var order = 0;

    	if (ids) {
    		// shuffle by given id...
    		var len = ids.length;
    		for (var i = 0; i < len; i++) {
    			// do shuffle!
    			var slide = this.slides[ids[i]];
    			if (slide) {
    				if (slide.visible) {
    					slide.order = order;
    					this.activeSlides[order] = slide;
    					order ++;
    				}
    			}
    		}
    	} else {
    		// simply rebuild the active slides...
    		for (var id in this.slides) {
    			var slide = this.slides[id];
    			if (slide) {
    				if (slide.visible) {
    					slide.order = order;
    					this.activeSlides[order] = slide;
    					order ++;
    				}
    			}
    		}
    	}

    	// always rebuild...
    	this.build();

    	// if to start.. then start
    	if (this.willStart) {
    		this.start();
    	}
    	return this;
    };


    /**
     * go to slide
     * @param slide the slide
     * @return this
     */
    this.goToSlide = function(slide) {
    	// get order from id...
    	if (!this.loaded) {
    		return this;
    	}
//    	QSLIDE.log('next: ' + slide.order);
//    	QSLIDE.log('container - fx:'); QSLIDE.log(this.fx);
//    	QSLIDE.log('slider - fx:'); QSLIDE.log(slide.fx);
    	if (slide.visible && this.currentSlide.order != slide.order) {
    		var next    = slide.order;
    		var current = this.currentSlide.order;
    		// calculate the range and run
    		var range = 0 - next * this.width;
        	var divider = Math.abs(next - current);
        	// limit speed divider to lower than 3 - otherwise it will be too fast
        	if (divider > 3) {
        		divider = 3;
        	}
    		// run it!!!
    		var fx = this.currentSlide.fx;
    		if (!fx) {
    			fx = this.fx;
    		}
    		var speed = fx.speed;
    		if (divider > 0) {
    			speed = parseInt(fx.speed/divider);
    		}
    		// move
    		$('#' + this.wrapperId).animate({
    			left:range
    		}, speed, fx.easing);

    		//re assign
    		this.currentSlide = slide;
    		// if nav, paint it...
    		if (this.navId) {
    			this.nav.paint(this);
    		}
    	}
    };


    /**
     * go to an id...
     * @param id the id of the slide
     */
    this.goTo = function(id) {
    	if (!this.loaded) {
    		return this;
    	}
    	if (this.slides[id]) {
    		return this.goToSlide(this.slides[id]);
    	}
    };


    /**
     * go to an number
     * @param num
     */
    this.goToNumber = function(num) {
    	if (!this.loaded) {
    		return this;
    	}
    	if (this.activeSlides[num]) {
    		return this.goToSlide(this.activeSlides[num]);
    	}
    };


    /**
     * jump - care for run state as well
     */
    this.jump = function(id) {
    	if (!this.loaded) {
    		return this;
    	}
    	// jump - so it pauses then comes back...
    	if (this.isStarted) {
    		this.pause();
        	this.goTo(id);
        	// rerun
        	var id = this.id;
        	QSLIDE.log('jump to: '); QSLIDE.log(QSLIDE.getContainer(id).fx);
        	this.isStarted = setTimeout(function(){
        		QSLIDE.getContainer(id).start();
        	}, QSLIDE.getContainer(id).fx.interval);
        	return this;
    	}

    	// otherwise, just jump
    	this.goTo(id);
    	return this;
    };


    /**
     * next slide
     */
    this.next = function() {
    	if (!this.loaded) {
    		return this;
    	}
    	var next = this.currentSlide.order + 1;
    	if (next >= this.activeSlides.length) {
    		next = 0;
    	}
    	QSLIDE.log('go to next: ' + next);
    	this.jump(this.activeSlides[next].id);
    };


    /**
     * prev slide
     */
    this.prev = function() {
    	if (!this.loaded) {
    		return this;
    	}
    	var prev = this.currentSlide.order - 1;
    	if (prev < 0) {
    		prev = this.activeSlides.length - 1;
    	}
    	QSLIDE.log('go to previous: ' + prev);
    	this.jump(this.activeSlides[prev].id);
    };

    /*-----------------------------[start doing the slides and all]-----------------------------*/
    /**
     * stop all slides
     */
    this.stop = function() {
    	if (!this.loaded) {
    		return this;
    	}
	    // stop slide
    	QSLIDE.log('stop ' + this.id);
    	this.goToNumber(0);
    	this.pause();
    };


    /**
     * pause slides
     */
    this.pause = function() {
    	if (!this.loaded) {
    		return this;
    	}
	    // stop slide
    	QSLIDE.log('pause ' + this.id);
    	if (this.isStarted) {
    		clearTimeout(this.isStarted);
    		this.isStarted = false;
    	}
    };


    /**
     * start the call...
     * @param callBack the onload call back function
     * @return void
     */
    this.start = function(callBack) {
        QSLIDE.log('start showing ' + this.id);
    	this.willStart = true;
    	if (callBack) {
    		externalOnloadFunction = callBack;
    	}
    	// start it
    	if (this.loaded) {
    		if (callBack) {
    			callBack();
    			// unset it...
    			externalOnloadFunction = callBack = false;
    		}
    		this.run();
    	}
    };


    /**
     * run it!
     * @return void
     */
    this.run = function() {
    	var id = this.id;
    	// just use go to...
    	var next = this.currentSlide.order + 1;
    	if (next >= this.activeSlides.length) {
    		next = 0;
    	}

    	// only run after a brief pause...
    	if (this.isStarted) {
    		this.goToNumber(next);
    	}

//    	QSLIDE.log('before: '); QSLIDE.log(this.currentSlide.fx); QSLIDE.log(this.currentSlide);
//    	QSLIDE.log('wait for: ' + this.currentSlide.fx.interval + ' before moving to ', this.activeSlides[next]);
    	this.isStarted = setTimeout(function(){
    		QSLIDE.getContainer(id).run();
    	}, this.currentSlide.fx.interval);
    	// note: use interval to override
//    	QSLIDE.log('after: '); QSLIDE.log(this.currentSlide.fx); QSLIDE.log(this.currentSlide);
    	return this;
    };


    /**
     * set effects (global)
     * @param speed the transition speed
	 * @param interval how long does it stay?
	 * @param easing type of easing
	 * @return void
	 */
    this.setFx = function(speed, interval, easing) {
    	// set all children fx as this as well...
    	var topFx = new QSLIDE_FX(speed, interval, easing);
    	this.fx = topFx;
    	$(this.activeSlides).each(function(){
    		this.setFx(topFx.speed, topFx.interval, topFx.easing);
    	});
    };


    /**
     * render navigation
     * @param containerId the id of the container
     * @param template    the template for the nav
     * @param sep         separator
     */
    this.renderNav = function(containerId, template, sep) {
    	this.navId = containerId;
    	this.nav   = new QSLIDE_NAV($('#' + containerId), template, sep);
    	// paint...
    	this.nav.paint(this);
    };

};


/**
 * slide class
 * @param obj the slide object
 * @param parentObj the container object
 * @param order self order
 */
var QSLIDE_SLIDE = function(obj, parentObj, order)
{
	// attributes...
	// 1. properties
	this.id      = obj.attr('id');
	this.title   = obj.attr('title');
    this.width   = parentObj.width;
    this.height  = parentObj.height;
    this.order   = order;
    this.fx		 = new QSLIDE_FX(parentObj.fx.speed, parentObj.fx.interval, parentObj.fx.easing);
    this.visible = true;

    // parent...
    this.parentObj = parentObj;

    // get display...
    if (obj.css('display') == 'none') {
    	this.visible = false;
    }

    // extra
	this.className = obj.attr('class');
	this.style	   = obj.attr('style');

    // set content to div...
    this.html = obj.html();

    // preload images if any...
    var images = $('#' + this.id + ' img');
    if (images.length > 0) {
    	parentObj.addImages(images);
    }
    // clean up
    obj.html(''); obj = null;

    /*-----------------------------[core]-----------------------------*/
    /**
     * build...
     * @return html
     */
    this.build = function() {
    	if (!this.visible) {
    		return '';
    	}

    	// otherwise...
    	var src = '<div'
    			  + ' id="' + this.id
    			  + '" title="' + this.title
    			  + '" class="' + this.className
    			  + '" style="' + this.style
    			  + ';float:left;width:'
    			  + this.width
    			  + ';height:'
    			  + this.height
    			  + ';overflow:hidden;">'
    			  + this.html
    			  + '</div>';
    	return src;
    };

    /*-----------------------------[slide control]-----------------------------*/
    /**
     * show slide
     * @return void
     */
    this.show = function() {
    	this.visible = true;
    };


    /**
     * hide slide
     * @return void
     */
    this.hide = function(id) {
    	this.visible = false;
    };


    /**
     * set effects (global)
     * @param speed the transition speed
	 * @param interval how long does it stay?
	 * @param easing type of easing
	 * @return void
	 */
    this.setFx = function(speed, interval, easing) {
    	// set all children fx as this as well...
    	this.fx = new QSLIDE_FX(speed, interval, easing);
    };
};